#include <iostream>
#include <type_traits>
#include <utility>

template <typename T> void f1(T &param) noexcept {}
// param is a reference

template <typename T> void f2(T &&param) noexcept {}
// param is a universal reference
template <typename T> void f3(T param) noexcept {}
// param is neither a pointer nor a reference
auto foo(const int &x) { return x; }

decltype(auto) bar(const int &x) { return x; }

int main(int argc, char *argv[]) {

  int x = 123;
  const int cx = x;
  const int &rx = x;

  f1(x);  // type(param) = int&
  f1(cx); // type(param) = const int&
  f1(rx); // type(param) = const int&

  f2(x);  // lvalue, type(param) = int&
  f2(cx); // lvalue, type(param) = const int&
  f2(rx); // lvalue, type(param) = const int&
  f2(12); // rvalue, type(param) = int&&

  f3(x);  // type(param) = int
  f3(cx); // type(param) = int
  f3(rx); // type(param) = int
  f3(12); // type(param) = int

  auto y = 123;       // type(x) = int
  const auto cy = y;  // type(cx) = const int
  const auto &ry = y; // type(rx) = const int&

  auto &&ury = y;   // type(urx) = int&
  auto &&urcy = cy; // type(urcx) = const int&
  auto &&urry = ry; // type(urrx) = const int&
  auto &&urrv = 12; // type(urrv) = int&&

  // decltype(auto), preserves types reference and cv-qualifiers, auto does not
  int z;
  const int cz = z;
  const int &crz = z;
  int &&z_ = 0;

  // preserves cv-qualifiers
  decltype(auto) y1 = crz;

  static_assert(std::is_same<const int &, decltype(y1)>::value == 1);
  // auto does not preserves cv-qualifiers
  auto y2 = crz;
  static_assert(std::is_same<int, decltype(y2)>::value == 1);

  // decltype(auto) preserve rvalue reference
  decltype(auto) z1 = std::move(z_);
  static_assert(std::is_same<int &&, decltype(z1)>::value == 1);

  // decltype(auto) is especially useful for writing a generic function's return
  static_assert(std::is_same<int, decltype(foo(1))>::value == 1);
  static_assert(std::is_same<const int &, decltype(bar(1))>::value == 1);
  return 0;
}

// reference collapsing

// T& & -> T&
// T& && -> T&
// T&& & -> T&
// T&& && -> T&&

// note & always wins. only T&& && == T&&
